/* winaudio.c */

/********************/
/* SOUND-INTERFACE  */
/* for MS-Windows   */
/* (C) 2003 by NAH6 */
/********************/

/*------------*/
/*  INCLUDES  */
/*------------*/

#include <memory.h>
#include <windows.h>
#include <mmsystem.h>

#include "filter.h"
#include "winaudio.h"
#include "sound.h"

/*--------------*/
/*  STRUCTURES  */
/*--------------*/

#ifndef _WIN32_WCE
union audio_mixctrldata
{
  MIXERCONTROLDETAILS_BOOLEAN  db[K_AUDIO_MAXMIXCHAN * K_AUDIO_MAXMIXITEM];
  MIXERCONTROLDETAILS_UNSIGNED du[K_AUDIO_MAXMIXCHAN * K_AUDIO_MAXMIXITEM];
  MIXERCONTROLDETAILS_SIGNED   ds[K_AUDIO_MAXMIXCHAN * K_AUDIO_MAXMIXITEM];
};
#endif

/*--------------*/
/*  PROTOTYPES  */
/*--------------*/

/* API-Support */
#ifndef _WIN32_WCE
static unsigned char audio__findsigline  (unsigned char *results,       \
                                          unsigned char sigins,         \
                                          const DWORD *fktnids,         \
                                          const struct audio_hwinfo *info);
#endif
static unsigned char audio__chkwavedev   (unsigned char out,            \
                                          unsigned char dev,            \
                                          unsigned char w44k,           \
                                          const struct audio_hwinfo *info);
static MMRESULT      audio__chkwave8k    (unsigned char out,            \
                                          unsigned char dev);

static void          audio__waventer     (struct audio_dseg *dseg, int *prio);
static void          audio__wavleave     (struct audio_dseg *dseg, int prio);
static signed short  audio__outstart     (struct audio_dseg *dseg);

static void          audio__transinport  (const struct audio_dseg *dseg, \
                                          unsigned char *port);
#ifndef _WIN32_WCE
static signed short  audio__setinpath    (const struct audio_dseg *dseg, \
                                          unsigned char port,            \
                                          unsigned short vol);
#endif
static void          audio__transoutport (const struct audio_dseg *dseg, \
                                          unsigned char *port);
#ifndef _WIN32_WCE
static signed short  audio__setoutpath   (const struct audio_dseg *dseg, \
                                          unsigned char port,            \
                                          unsigned short vol);
#endif

/* Audio-Callback */
static void CALLBACK audio__callback_in  (HWAVEIN hwi, UINT msg, DWORD p0, \
                                          DWORD p1, DWORD p2);
static void CALLBACK audio__callback_out (HWAVEOUT hwo, UINT msg, DWORD p0, \
                                          DWORD p1, DWORD p2);
static DWORD WINAPI  audio__intthread    (LPVOID ptr);
static void          audio__rxchunk      (struct audio_dseg *dseg,  \
                                          const signed short *data, \
                                          unsigned short len);
static void          audio__txchunk      (struct audio_dseg *dseg, \
                                          signed short *data,      \
                                          unsigned short len);
static void          audio__rampup       (signed short *data);
static void          audio__rampdown     (signed short *data);

/* Mixer-Control */
#ifndef _WIN32_WCE
static void          audio__getmixinfo   (struct audio_mixoutinfo *info,       \
                                          unsigned char mixdev,                \
                                          unsigned char mixout);
static unsigned long audio__getmixdlen   (const struct audio_mixoutinfo *info);

static signed short  audio__initmixout   (const struct audio_mixoutinfo *info, \
                                          unsigned char mixdev);
static signed short  audio__savemixstate (void *state,                         \
                                          const struct audio_mixoutinfo *info, \
                                          unsigned char mixdev);
static signed short  audio__loadmixstate (const void *state,                   \
                                          const struct audio_mixoutinfo *info, \
                                          unsigned char mixdev);
static signed short  audio__setmixlvl    (unsigned short mixlvl,               \
                                          unsigned char mixmux,                \
                                          const struct audio_mixctrlinfo *info,\
                                          unsigned char mixdev);

static signed short  audio___setmixpar_b (union audio_mixctrldata *data,       \
                                          LONG value,                          \
                                          const struct audio_mixctrlinfo *info);
static signed short  audio___setmixpar_u (union audio_mixctrldata *data,       \
                                          DWORD value,                         \
                                          const struct audio_mixctrlinfo *info);
static signed short  audio___setmixpar_s (union audio_mixctrldata *data,       \
                                          LONG value,                          \
                                          const struct audio_mixctrlinfo *info);
static signed short  audio___setmixpar_m (union audio_mixctrldata *data,       \
                                          unsigned char value,                 \
                                          const struct audio_mixctrlinfo *info);
static signed short  audio___getmixdata  (union audio_mixctrldata *data,       \
                                          const struct audio_mixctrlinfo *info,\
                                          unsigned char mixdev);
static signed short  audio___setmixdata  (const union audio_mixctrldata *data, \
                                          const struct audio_mixctrlinfo *info,\
                                          unsigned char mixdev);
static signed short  audio___prepmixhdr  (MIXERCONTROLDETAILS *datahdr,        \
                                          union audio_mixctrldata *data,       \
                                          const struct audio_mixctrlinfo *info);
static signed short  audio___chkmixinfo  (const struct audio_mixctrlinfo *info);
#endif

/* Error-Conversion */
static signed short audio__errconv (MMRESULT rc);

/*-------------*/
/*  VARIABLEN  */
/*-------------*/

static const unsigned short audio__ramptbl[K_FILTER_CHUNKSIZE_8K] =
{
  0xFD85, 0xFAEB, 0xF82F, 0xF553, 0xF259, 0xEF3F, 0xEC04, 0xE8AA, \
  0xE533, 0xE19D, 0xDDEB, 0xDA1D, 0xD633, 0xD22E, 0xCE0F, 0xC9D7, \
  0xC586, 0xC11F, 0xBCA2, 0xB813, 0xB370, 0xAEBE, 0xA9FD, 0xA52F, \
  0xA054, 0x9B6E, 0x967D, 0x9184, 0x8C82, 0x8785, 0x8285, 0x7D7A, \
  0x787A, 0x737D, 0x6E7B, 0x6982, 0x6491, 0x5FAB, 0x5AD0, 0x5602, \
  0x5141, 0x4C8F, 0x47EC, 0x435D, 0x3EE0, 0x3A79, 0x3628, 0x31F0, \
  0x2DD1, 0x29CC, 0x25E2, 0x2214, 0x1E62, 0x1ACC, 0x1755, 0x13FB, \
  0x10C0, 0x0DA6, 0x0AAC, 0x07D0, 0x0514, 0x027A, 0x0000, 0x0000, \
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, \
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
};

/*----*/

#ifndef _WIN32_WCE
static const DWORD audio__cfgportin[K_AUDIO_MAXPORTIN+1] =
{
  MIXERLINE_COMPONENTTYPE_DST_WAVEIN,        /* Out */
  MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE,    /* In1 */
  MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY,     /* In2 */
  MIXERLINE_COMPONENTTYPE_SRC_LINE,          /* In3 */
  MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE      /* In4 */
};
#endif

#ifndef _WIN32_WCE
static const DWORD audio__cfgportout[K_AUDIO_MAXPORTOUT][2] =
{
  { MIXERLINE_COMPONENTTYPE_DST_SPEAKERS,    /* Out1/Out */
    MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT,     /* Out1/In  */
  },
  {
    MIXERLINE_COMPONENTTYPE_DST_HEADPHONES,  /* Out2/Out */
    MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT,     /* Out2/In  */
  },
  {
    MIXERLINE_COMPONENTTYPE_DST_LINE,        /* Out3/Out */
    MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT,     /* Out3/In  */
  },
  {
    MIXERLINE_COMPONENTTYPE_DST_TELEPHONE,   /* Out4/Out */
    MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT,     /* Out4/In  */
  }
};
#endif

/*============================================================================*/
/*           A  U  D  I  O   -   I  /  O   -   L  I  B  R  A  R  Y            */
/*============================================================================*/

/*----------------*/
/*  AUDIO-DETECT  */
/*----------------*/
                                                                    /* [7201] */
void audio_hwdetect (struct audio_hwinfo *info)      /* Detect Audio-Hardware */
{
#ifndef _WIN32_WCE
  unsigned char y,z;
#endif
  unsigned char x;
  MMRESULT rc;
  UINT n;

  /* Input-Devices */
  n = waveInGetNumDevs ();
  for (x=0; x<K_AUDIO_MAXDEVIN; x++)
  {
    if (x < n)
    {
      rc = waveInGetDevCaps (x, &(info->wavein[x].caps), sizeof (WAVEINCAPS));
      if (rc == MMSYSERR_NOERROR)
        info->wavein[x].valid = K_AUDIO_DEVICE_OK;
      else
        info->wavein[x].valid = K_AUDIO_DEVICE_ERROR;
    }
    else
      info->wavein[x].valid = K_AUDIO_DEVICE_NOHW;
  }

  /* Output-Devices */
  n = waveOutGetNumDevs ();
  for (x=0; x<K_AUDIO_MAXDEVOUT; x++)
  {
    if (x < n)
    {
      rc = waveOutGetDevCaps (x, &(info->waveout[x].caps), \
                              sizeof (WAVEOUTCAPS));
      if (rc == MMSYSERR_NOERROR)
        info->waveout[x].valid = K_AUDIO_DEVICE_OK;
      else
        info->waveout[x].valid = K_AUDIO_DEVICE_ERROR;
    }
    else
      info->waveout[x].valid = K_AUDIO_DEVICE_NOHW;
  }

  /* Mixer-Devices */
#ifndef _WIN32_WCE
  n = mixerGetNumDevs ();
  for (x=0; x<K_AUDIO_MAXDEVMIX; x++)
  {
    if (x < n)
    {
      rc = mixerGetDevCaps (x, &(info->mixer[x].caps), sizeof (MIXERCAPS));
      if (rc == MMSYSERR_NOERROR)
        info->mixer[x].valid = K_AUDIO_DEVICE_OK;
      else
        info->mixer[x].valid = K_AUDIO_DEVICE_ERROR;
    }
    else
      info->mixer[x].valid = K_AUDIO_DEVICE_NOHW;
  }

  /* Signal-Lines */
  for (x=0; x<K_AUDIO_MAXDEVMIX; x++)
  {
    if (x < n)
    {
      if (info->mixer[x].valid == K_AUDIO_DEVICE_OK)
      {
        for (y=0; y<K_AUDIO_MAXMIXOUT; y++)
        {
          if (y < info->mixer[x].caps.cDestinations)
          {
            info->sigline[x][y][0].caps.cbStruct = sizeof (MIXERLINE);
            info->sigline[x][y][0].caps.dwDestination = y;
            info->sigline[x][y][0].caps.dwSource = 0;
            rc = mixerGetLineInfo ((HMIXEROBJ)x,                   \
                                   &(info->sigline[x][y][0].caps), \
                                   MIXER_OBJECTF_MIXER |           \
                                   MIXER_GETLINEINFOF_DESTINATION);
            if (rc == MMSYSERR_NOERROR)
            {
              info->sigline[x][y][0].valid = K_AUDIO_DEVICE_OK;
              for (z=1; z<=K_AUDIO_MAXMIXIN; z++)
              {
                if (z <= info->sigline[x][y][0].caps.cConnections)
                {
                  info->sigline[x][y][z].caps.cbStruct = sizeof (MIXERLINE);
                  info->sigline[x][y][z].caps.dwDestination = y;
                  info->sigline[x][y][z].caps.dwSource = z - 1;
                  rc = mixerGetLineInfo ((HMIXEROBJ)x,                   \
                                         &(info->sigline[x][y][z].caps), \
                                         MIXER_OBJECTF_MIXER |           \
                                         MIXER_GETLINEINFOF_SOURCE);
                  if (rc == MMSYSERR_NOERROR)
                    info->sigline[x][y][z].valid = K_AUDIO_DEVICE_OK;
                  else
                    info->sigline[x][y][z].valid = K_AUDIO_DEVICE_ERROR;
                }
                else
                  info->sigline[x][y][z].valid = K_AUDIO_DEVICE_NOHW;
              }
            }
            else
              for (z=0; z<=K_AUDIO_MAXMIXIN; z++)
                info->sigline[x][y][z].valid = K_AUDIO_DEVICE_ERROR;
          }
          else
            for (z=0; z<=K_AUDIO_MAXMIXIN; z++)
              info->sigline[x][y][z].valid = K_AUDIO_DEVICE_NOHW;
        }
      }
      else
        for (y=0; y<K_AUDIO_MAXMIXOUT; y++)
          for (z=0; z<=K_AUDIO_MAXMIXIN; z++)
            info->sigline[x][y][z].valid = K_AUDIO_DEVICE_ERROR;
    }
    else
      for (y=0; y<K_AUDIO_MAXMIXOUT; y++)
        for (z=0; z<=K_AUDIO_MAXMIXIN; z++)
          info->sigline[x][y][z].valid = K_AUDIO_DEVICE_NOHW;
  }
#endif
}

/*-----------------*/
/*  AUDIO-AUTOCFG  */
/*-----------------*/                                               /* [7202] */
                                         /* Auto-Select Audio-Hardware-Config */
void audio_hwautocfg (struct audio_hwconfig *config, \
                      const struct audio_hwinfo *info)
{
#ifndef _WIN32_WCE
  const struct audio_hwinfo_sigline *line;
#endif
  unsigned char r[K_AUDIO_MAXPORTIN+2];
  unsigned char n,m,x,y;

  /* Route input to A/D */
#ifndef _WIN32_WCE
  n = audio__findsigline (r, K_AUDIO_MAXPORTIN, audio__cfgportin, info);
#else
  n = 0x81;
  r[0] = 0;
  r[1] = 0;
  r[2] = 0;
#endif
  config->devin_mixmap = n & 0x7F;
  if (n & 0x80)
  {
    config->devin_mixdev = r[0];
    config->devin_mixout = r[1];
  }
  else
  {
    config->devin_mixdev = 0;
    config->devin_mixout = 0;
  }
  for (x=0; x<K_AUDIO_MAXPORTIN; x++)
  {
    if (n & 0x01)
    {
      config->devin_mixin [x] = r[x+2];
      config->devin_mixlvl[x] = 0xFFFF;
    }
    else
    {
      config->devin_mixin [x] = 0;
      config->devin_mixlvl[x] = 0;
    }
    n >>= 1;
  }

  /* Route D/A to output */
  m = 0;
  y = 0x01;
  for (x=0; x<K_AUDIO_MAXPORTOUT; x++)
  {
#ifndef _WIN32_WCE
    n = audio__findsigline (r, 1, &(audio__cfgportout[x][0]), info);
#else
    if (!x)
    {
      n = 0x81;
      r[0] = 0;
      r[1] = 0;
      r[2] = 0;
    }
    else
      n = 0;
#endif
    if (n & 0x80)
    {
      config->devout_mixdev[x] = r[0];
      config->devout_mixout[x] = r[1];
    }
    else
    {
      config->devout_mixdev[x] = 0;
      config->devout_mixout[x] = 0;
    }
    if (n & 0x01)
    {
      m |= y;
      config->devout_mixin[x] = r[2];
      if (x < 2) /* Spkr,Head */
        config->devout_mixlvl[x] = K_AUDIO_STDVOLUME;
      else
        config->devout_mixlvl[x] = 0xFFFF;
    }
    else
    {
      config->devout_mixin [x] = 0;
      config->devout_mixlvl[x] = 0;
    }
    y <<= 1;
  }
  config->devout_mixmap = m;

  /* Select A/D-converter */
#ifndef _WIN32_WCE
  if (config->devin_mixmap)
  {
    line = &(info->sigline[config->devin_mixdev][config->devin_mixout][0]);
    if (line->valid == K_AUDIO_DEVICE_OK)
      if (line->caps.Target.dwType == MIXERLINE_TARGETTYPE_WAVEIN)
      {
        x = (unsigned char)(line->caps.Target.dwDeviceID);
        n = audio__chkwavedev (0, x, 0, info);
        if (n)
          goto wavein_found;
      }
  }
#endif
  for (x=0; x<K_AUDIO_MAXDEVIN; x++)
  {
    n = audio__chkwavedev (0, x, 1, info);
    if (n)
      goto wavein_found;
  }
  for (x=0; x<K_AUDIO_MAXDEVIN; x++)
  {
    n = audio__chkwavedev (0, x, 0, info);
    if (n)
      goto wavein_found;
  }
  x = 0;
  wavein_found:
  config->devin_wave = x;

  /* Select D/A-converter */
#ifndef _WIN32_WCE
  m = config->devout_mixmap;
  for (y=0; y<K_AUDIO_MAXPORTOUT; y++)
  {
    if (m & 0x01)
    {
      line = &(info->sigline[config->devout_mixdev[y]] \
                            [config->devout_mixout[y]] \
                            [config->devout_mixin [y]+1]);
      if (line->valid == K_AUDIO_DEVICE_OK)
        if (line->caps.Target.dwType == MIXERLINE_TARGETTYPE_WAVEOUT)
        {
          x = (unsigned char)(line->caps.Target.dwDeviceID);
          n = audio__chkwavedev (1, x, 0, info);
          if (n)
            goto waveout_found;
        }
    }
    m >>= 1;
  }
#endif
  if (config->devin_wave < K_AUDIO_MAXDEVOUT)
  {
    x = config->devin_wave;
    n = audio__chkwavedev (1, x, 0, info);
    if (n)
      goto waveout_found;
  }
  for (x=0; x<K_AUDIO_MAXDEVOUT; x++)
  {
    n = audio__chkwavedev (1, x, 1, info);
    if (n)
      goto waveout_found;
  }
  for (x=0; x<K_AUDIO_MAXDEVOUT; x++)
  {
    n = audio__chkwavedev (1, x, 0, info);
    if (n)
      goto waveout_found;
  }
  x = 0;
  waveout_found:
  config->devout_wave = x;
}

/*====*/

#ifndef _WIN32_WCE
                                                          /* Find Signal-Line */
static unsigned char audio__findsigline (unsigned char *results, \
                                         unsigned char sigins,   \
                                         const DWORD *fktnids,   \
                                         const struct audio_hwinfo *info)
{
  const struct audio_hwinfo_sigline *mixout,*mixin;
  unsigned char n,m,x,y,z;

  n = 0;
  for (x=0; x<K_AUDIO_MAXDEVMIX; x++)
    for (y=0; y<K_AUDIO_MAXMIXOUT; y++)
    {
      mixout = &(info->sigline[x][y][0]);
      if (mixout->valid == K_AUDIO_DEVICE_NOHW)
        break;
      else if (mixout->valid == K_AUDIO_DEVICE_OK)
        if (mixout->caps.dwComponentType == *fktnids)
        {
          n = 0x80;
          *results++ = x; /* dev */
          *results++ = y; /* out */
          mixout++;
          m = 0x01;
          while (sigins--)
          {
            mixin = mixout;
            fktnids++;
            for (z=0; z<K_AUDIO_MAXMIXIN; z++)
            {
              if (mixin->valid == K_AUDIO_DEVICE_NOHW)
                break;
              else if (mixin->valid == K_AUDIO_DEVICE_OK)
                if (mixin->caps.dwComponentType == *fktnids)
                {
                  n |= m;
                  *results = z; /* in */
                  break;
                }
              mixin++;
            }
            m <<= 1;
            results++;
          }
          goto done;
        }
    }
  done:
  return n;
}
#endif

/*----*/
                                                         /* Check Wave-Device */
static unsigned char audio__chkwavedev (unsigned char out, unsigned char dev, \
                                        unsigned char w44k, \
                                        const struct audio_hwinfo *info)
{
  MMRESULT rc;

  /* Check valid */
  if (out)
  {
    if (info->waveout[dev].valid != K_AUDIO_DEVICE_OK)
      return 0;
  }
  else
    if (info->wavein[dev].valid != K_AUDIO_DEVICE_OK)
      return 0;

  /* Check 44khz */
  if (w44k)
    if (out)
    {
      if (((info->waveout[dev].caps.dwFormats & WAVE_FORMAT_4S16) == 0) \
        || (info->waveout[dev].caps.wChannels < 2))
        return 0;
    }
    else
      if (((info->wavein[dev].caps.dwFormats & WAVE_FORMAT_4S16) == 0) \
        || (info->wavein[dev].caps.wChannels < 2))
        return 0;

  /* Check 8khz */
  rc = audio__chkwave8k (out, dev);
  if (rc != MMSYSERR_NOERROR)
    return 0; /* err */
  return 1;   /* ok */
}

/*----*/

static MMRESULT audio__chkwave8k (unsigned char out, unsigned char dev)
{
  WAVEFORMATEX wfx;
  MMRESULT rc;

  wfx.wFormatTag      = WAVE_FORMAT_PCM;
  wfx.nChannels       = 1;
  wfx.nSamplesPerSec  = 8000;
  wfx.nAvgBytesPerSec = 16000;
  wfx.nBlockAlign     = 2;
  wfx.wBitsPerSample  = 16;
  wfx.cbSize          = 0;
  if (out)
    rc = waveOutOpen (NULL, dev, &wfx, 0, 0, CALLBACK_NULL | WAVE_FORMAT_QUERY);
  else
    rc = waveInOpen (NULL, dev, &wfx, 0, 0, CALLBACK_NULL | WAVE_FORMAT_QUERY);
  return rc;
}

/*-----------------*/
/*  AUDIO-INSTALL  */
/*-----------------*/                                               /* [7203] */
                                                      /* Install Audio-Driver */
signed short audio_install (struct audio_dseg *dseg, \
                            const struct audio_hwconfig *config)
{
  unsigned char x,y;
  unsigned short v;
  unsigned long d;
  signed short n;
  MMRESULT rc;
  HANDLE heap;

  /* Check Pointer */
  if ((dseg == NULL) || (config == NULL))
    return E_AUDIO_NULLPTR;

  /* Data-Init */
  memcpy (&(dseg->hwconfig), config, sizeof (struct audio_hwconfig));
  heap = GetProcessHeap ();

  /* Check Wave-Input */
  rc = audio__chkwave8k (0, dseg->hwconfig.devin_wave);
  if (rc != MMSYSERR_NOERROR)
    goto err_exit;

  /* Check Wave-Output */
  rc = audio__chkwave8k (1, dseg->hwconfig.devout_wave);
  if (rc != MMSYSERR_NOERROR)
  {
    err_exit:
    n = audio__errconv (rc);
    return n;
  }

  /* Setup Mixer-Semaphore */
  InitializeCriticalSection (&(dseg->mixlock));

  /* Open Input-Mixer */
  dseg->mixin_sel = 0x00;
  dseg->mixin_info = NULL;
  dseg->mixin_bak  = NULL;
#ifndef _WIN32_WCE
  if (dseg->hwconfig.devin_mixmap)
  {
    dseg->mixin_info = HeapAlloc (heap, 0, sizeof (struct audio_mixoutinfo));
    if (dseg->mixin_info != NULL)
    {
      audio__getmixinfo (dseg->mixin_info, dseg->hwconfig.devin_mixdev, \
                         dseg->hwconfig.devin_mixout);
      d = audio__getmixdlen (dseg->mixin_info);
      dseg->mixin_bak = HeapAlloc (heap, 0, d);
      if (dseg->mixin_bak != NULL)
        audio__savemixstate (dseg->mixin_bak, dseg->mixin_info, \
                             dseg->hwconfig.devin_mixdev);
      audio__initmixout (dseg->mixin_info, dseg->hwconfig.devin_mixdev);
    }
  }
#endif

  /* Open Output-Mixer */
  y = dseg->hwconfig.devout_mixmap;
  for (x=0; x<K_AUDIO_MAXPORTOUT; x++)
  {
    dseg->mixout_info[x] = NULL;
    dseg->mixout_bak [x] = NULL;
    if (y & 0x01)
    {
#ifndef _WIN32_WCE
      dseg->mixout_info[x] = HeapAlloc (heap, 0, \
                             sizeof (struct audio_mixoutinfo));
      if (dseg->mixout_info[x] != NULL)
      {
        audio__getmixinfo (dseg->mixout_info[x], \
                           dseg->hwconfig.devout_mixdev[x], \
                           dseg->hwconfig.devout_mixout[x]);
        d = audio__getmixdlen (dseg->mixout_info[x]);
        dseg->mixout_bak[x] = HeapAlloc (heap, 0, d);
        if (dseg->mixout_bak[x] != NULL)
          audio__savemixstate (dseg->mixout_bak[x], dseg->mixout_info[x], \
                               dseg->hwconfig.devout_mixdev[x]);
        audio__initmixout (dseg->mixout_info[x], dseg->hwconfig.devout_mixdev[x]);
      }
#else
      if (!x)
      {
        d = 0L;
        rc = waveOutGetVolume ((HWAVEOUT)(dseg->hwconfig.devout_wave), &d);
        if (rc == MMSYSERR_NOERROR)
        {
          dseg->mixout_bak[0] = HeapAlloc (heap, 0, sizeof (unsigned long));
          if (dseg->mixout_bak[0] != NULL)
            *((unsigned long *)(dseg->mixout_bak[0])) = d;
        }
        waveOutSetVolume ((HWAVEOUT)(dseg->hwconfig.devout_wave), 0L);
      }
#endif
    }
    y >>= 1;
  }

  /* Enable Input-Mixer */
#ifndef _WIN32_WCE
  audio_firstinport (dseg, &x);
  if (x)
  {
    v = dseg->hwconfig.devin_mixlvl[x-1];
    if (v)
      audio__setinpath (dseg, x, v);
    dseg->mixin_sel = x;
  }
#else
  dseg->mixin_sel = 1;
#endif

  /* Enable Output-Mixer */
#ifndef _WIN32_WCE
  for (x=0; x<K_AUDIO_MAXPORTOUT; x++)
    if (dseg->mixout_info[x] != NULL)
    {
      v = dseg->hwconfig.devout_mixlvl[x];
      audio__setoutpath (dseg, (unsigned char)(x+1), v);
    }
#else
  v = dseg->hwconfig.devout_mixlvl[0];
  d = v;
  d = d << 16;
  d = d | v;
  waveOutSetVolume ((HWAVEOUT)(dseg->hwconfig.devout_wave), d);
#endif
  return 0;
}

/*----*/
                                                                    /* [7204] */
signed short audio_remove (struct audio_dseg *dseg)    /* Remove Audio-Driver */
{
#ifndef _WIN32_WCE
  unsigned char x;
  signed short m;
#endif
  signed short n;
  HANDLE heap;

  /* Check Pointer */
  if (dseg == NULL)
    return E_AUDIO_NULLPTR;

  /* Data-Init */
  heap = GetProcessHeap ();
  n = 0;

  /* Mute Output-Mixer */
#ifndef _WIN32_WCE
  for (x=0; x<K_AUDIO_MAXPORTOUT; x++)
    if (dseg->mixout_info[x] != NULL)
    {
      m = audio__initmixout (dseg->mixout_info[x], \
                             dseg->hwconfig.devout_mixdev[x]);
      if (n >= 0)
        n = m;
    }
#else
  waveOutSetVolume ((HWAVEOUT)(dseg->hwconfig.devout_wave), 0L);
#endif

  /* Mute Input-Mixer */
#ifndef _WIN32_WCE
  if (dseg->mixin_info != NULL)
  {
    m = audio__initmixout (dseg->mixin_info, dseg->hwconfig.devin_mixdev);
    if (n >= 0)
      n = m;
  }
#endif

  /* Close Output-Mixer */
#ifndef _WIN32_WCE
  for (x=0; x<K_AUDIO_MAXPORTOUT; x++)
    if (dseg->mixout_info[x] != NULL)
    {
      if (dseg->mixout_bak[x] != NULL)
      {
        m = audio__loadmixstate (dseg->mixout_bak[x], dseg->mixout_info[x], \
                               dseg->hwconfig.devout_mixdev[x]);
        if (n >= 0)
          n = m;
        HeapFree (heap, 0, dseg->mixout_bak[x]);
      }
      HeapFree (heap, 0, dseg->mixout_info[x]);
    }
#else
  if (dseg->mixout_bak[0] != NULL)
  {
    waveOutSetVolume ((HWAVEOUT)(dseg->hwconfig.devout_wave), \
                      *((unsigned long *)(dseg->mixout_bak[0])));
    HeapFree (heap, 0, dseg->mixout_bak[0]);
  }
#endif

  /* Close Input-Mixer */
#ifndef _WIN32_WCE
  if (dseg->mixin_info != NULL)
  {
    if (dseg->mixin_bak != NULL)
    {
      m = audio__loadmixstate (dseg->mixin_bak, dseg->mixin_info, \
                               dseg->hwconfig.devin_mixdev);
      if (n >= 0)
        n = m;
      HeapFree (heap, 0, dseg->mixin_bak);
    }
    HeapFree (heap, 0, dseg->mixin_info);
  }
#endif

  /* Remove Mixer-Semaphore */
  DeleteCriticalSection (&(dseg->mixlock));
  return n;
}

/*----*/                                                            /* [7205] */
                                                         /* Read Audio-Config */
signed short audio_getconfig (const struct audio_dseg *dseg, \
                              struct audio_hwconfig *config)
{
  /* Check Pointer */
  if ((dseg == NULL) || (config == NULL))
    return E_AUDIO_NULLPTR;

  /* Read Config */
  memcpy (config, &(dseg->hwconfig), sizeof (struct audio_hwconfig));
  return 0;
}

/*----------------------------------------------------------------------------*/

/*--------------*/
/*  AUDIO-OPEN  */
/*--------------*/                                                  /* [7206] */
                                                            /* Open Audio-I/O */
signed short audio_open (struct audio_dseg *dseg, \
                         const struct audio_openopt *opt)
{
  signed short *p,*q,n;
  unsigned long a,d;
  unsigned char x,y;
  WAVEFORMATEX wfx;
  MMRESULT rc;
  HANDLE heap;
  BOOL b;

  /* Check Pointer */
  if ((dseg == NULL) || (opt == NULL))
    return E_AUDIO_NULLPTR;

  /* Check Options */
  if (opt->bufferlen < (K_FILTER_CHUNKSIZE_8K << 3))
    return E_AUDIO_PAR_BUFLEN;
  if ((opt->triggerlvl == 0) || (opt->triggerlvl >= opt->bufferlen))
    return E_AUDIO_PAR_TRIGGER;
  if (opt->samplerate >= K_FILTER_SAMPLERATES)
    return E_AUDIO_PAR_RATE;
  if (opt->winchunks == 0)
    return E_AUDIO_PAR_WINCHUNK;
  memcpy (&(dseg->openopt), opt, sizeof (struct audio_openopt));

  /* Allocate Memory */
  a = filter_chunklen[dseg->openopt.samplerate];
  a *= dseg->openopt.winchunks;
  d = (dseg->openopt.bufferlen << 2) + (K_FILTER_CHUNKSIZE_8K << 2) - 4 + \
      (K_FILTER_HISTORYLEN << 2) + (a << 3) + (a << 2);
  heap = GetProcessHeap ();
  p = HeapAlloc (heap, 0, d);
  if (p == NULL)
    return E_AUDIO_NOMEM;
  q = p;

  /* Init Common-Data */
  InitializeCriticalSection (&(dseg->wavlock));
  for (x=0; x<2; x++)
  {
    dseg->hdrevtbuf[x] = CreateEvent (NULL, TRUE, 0, NULL);
    if (dseg->hdrevtbuf[x] == NULL)
    {
      n = E_AUDIO_NOMEM;
      goto err_exit11;
    }
  }

  /* Init Input-Data */
  dseg->inpcmevt = CreateEvent (NULL, TRUE, 0, NULL);
  if (dseg->inpcmevt == NULL)
  {
    n = E_AUDIO_NOMEM;
    goto err_exit10;
  }
  dseg->inflags = K_AUDIO_FLAGINSTOP;
  dseg->inpcmwr = 0L;
  dseg->inpcmrd = 0L;
  dseg->inpcmbuf = p;
  p += dseg->openopt.bufferlen + K_FILTER_CHUNKSIZE_8K - 1;
  dseg->inhdrint = NULL;
  dseg->inhdrpos = 2;
  dseg->inhdrpcm = p;
  for (x=0; x<K_FILTER_HISTORYLEN; x++)
    *p++ = 0;
  for (x=0; x<3; x++)
  {
    dseg->inhdrbuf[x].lpData          = (void *)p;
    dseg->inhdrbuf[x].dwBufferLength  = a << 1;
    dseg->inhdrbuf[x].dwBytesRecorded = 0L;
    dseg->inhdrbuf[x].dwUser          = 0L;
    dseg->inhdrbuf[x].dwFlags         = 0L;
    dseg->inhdrbuf[x].dwLoops         = 0L;
    p += a;
  }

  /* Init Output-Data */
  dseg->outpcmevt = CreateEvent (NULL, TRUE, 1, NULL);
  if (dseg->outpcmevt == NULL)
  {
    n = E_AUDIO_NOMEM;
    goto err_exit9;
  }
  dseg->outflags = K_AUDIO_FLAGOUTSTOP;
  dseg->outpcmwr = 0L;
  dseg->outpcmrd = 0L;
  dseg->outpcmbuf = p;
  for (x=0; x<K_FILTER_HISTORYLEN; x++)
    *p++ = 0;
  p += dseg->openopt.bufferlen + K_FILTER_CHUNKSIZE_8K - 1;
  dseg->outhdrint = NULL;
  dseg->outhdrpos = 2;
  dseg->outhdrpcm = p;
  memset (p, 0x00, a << 2);
  for (x=0; x<3; x++)
  {
    dseg->outhdrbuf[x].lpData          = (void *)p;
    dseg->outhdrbuf[x].dwBufferLength  = a << 1;
    dseg->outhdrbuf[x].dwBytesRecorded = 0L;
    dseg->outhdrbuf[x].dwUser          = 0L;
    dseg->outhdrbuf[x].dwFlags         = 0L;
    dseg->outhdrbuf[x].dwLoops         = 0L;
    p += a;
  }
  dseg->outstate = K_AUDIO_OSTATE_SYNTH;
  dseg->outidle  = 0xFFFFFFFFL;

  /* Open Input-Device */
  a = filter_samplerate[dseg->openopt.samplerate];
  wfx.wFormatTag      = WAVE_FORMAT_PCM;
  wfx.nChannels       = 1;
  wfx.nSamplesPerSec  = a;
  wfx.nAvgBytesPerSec = a << 1;
  wfx.nBlockAlign     = 2;
  wfx.wBitsPerSample  = 16;
  wfx.cbSize          = 0;
  rc = waveInOpen (&(dseg->inhandle), dseg->hwconfig.devin_wave, &wfx, \
                   (DWORD)audio__callback_in, (DWORD)dseg, CALLBACK_FUNCTION);
  if (rc != MMSYSERR_NOERROR)
  {
    n = audio__errconv (rc);
    goto err_exit8;
  }
  rc = waveInStop (dseg->inhandle);
  if (rc != MMSYSERR_NOERROR)
  {
    n = audio__errconv (rc);
    goto err_exit7;
  }
  for (x=0; x<3; x++)
  {
    rc = waveInPrepareHeader (dseg->inhandle, dseg->inhdrbuf + x, \
                              sizeof (WAVEHDR));
    if (rc != MMSYSERR_NOERROR)
    {
      n = audio__errconv (rc);
      goto err_exit6;
    }
  }
  for (x=0; x<2; x++)
  {
    rc = waveInAddBuffer (dseg->inhandle, dseg->inhdrbuf + x, sizeof (WAVEHDR));
    if (rc != MMSYSERR_NOERROR)
      goto err_exit1;
  }

  /* Open Output-Device */
  wfx.wFormatTag      = WAVE_FORMAT_PCM;
  wfx.nChannels       = 1;
  wfx.nSamplesPerSec  = a;
  wfx.nAvgBytesPerSec = a << 1;
  wfx.nBlockAlign     = 2;
  wfx.wBitsPerSample  = 16;
  wfx.cbSize          = 0;
  rc = waveOutOpen (&(dseg->outhandle), dseg->hwconfig.devout_wave, &wfx, \
                    (DWORD)audio__callback_out, (DWORD)dseg, CALLBACK_FUNCTION);
  if (rc != MMSYSERR_NOERROR)
  {
    err_exit1:
    n = audio__errconv (rc);
    goto err_exit5;
  }
  rc = waveOutPause (dseg->outhandle);
  if (rc != MMSYSERR_NOERROR)
  {
    n = audio__errconv (rc);
    goto err_exit4;
  }
  for (x=0; x<3; x++)
  {
    rc = waveOutPrepareHeader (dseg->outhandle, dseg->outhdrbuf + x, \
                               sizeof (WAVEHDR));
    if (rc != MMSYSERR_NOERROR)
    {
      n = audio__errconv (rc);
      goto err_exit3;
    }
  }
  for (x=0; x<2; x++)
  {
    rc = waveOutWrite (dseg->outhandle, dseg->outhdrbuf + x, sizeof (WAVEHDR));
    if (rc != MMSYSERR_NOERROR)
    {
      n = audio__errconv (rc);
      goto err_exit2;
    }
  }

  /* Create Int-Thread */
  dseg->intthread = CreateThread (NULL, 0, audio__intthread, dseg, \
                                  CREATE_SUSPENDED, &(dseg->intthreadid));
  if (dseg->intthread == NULL)
  {
    n = E_AUDIO_THREAD;
    goto err_exit2;
  }
#ifndef _WIN32_WCE
  b = SetThreadPriority (dseg->intthread, K_AUDIO_INTPRIO);
#else
  b = CeSetThreadPriority (dseg->intthread, K_AUDIO_INTPRIO);
#endif
  if (!b)
  {
    /* Close Thread */
    TerminateThread (dseg->intthread, 0xFFFFFFFFL);
    CloseHandle (dseg->intthread);
    n = E_AUDIO_PRIO;
    /* Close Output */
    err_exit2:
    waveOutReset (dseg->outhandle);
    x = 3;
    err_exit3:
    y = x;
    while (y)
    {
      y--;
      waveOutUnprepareHeader (dseg->outhandle, dseg->outhdrbuf + y, \
                              sizeof (WAVEHDR));
    }
    err_exit4:
    waveOutClose (dseg->outhandle);
    /* Close Input */
    err_exit5:
    waveInReset (dseg->inhandle);
    x = 3;
    err_exit6:
    y = x;
    while (y)
    {
      y--;
      waveInUnprepareHeader (dseg->inhandle, dseg->inhdrbuf + y, \
                             sizeof (WAVEHDR));
    }
    err_exit7:
    waveInClose (dseg->inhandle);
    /* Close Events */
    err_exit8:
    CloseHandle (dseg->outpcmevt);
    err_exit9:
    CloseHandle (dseg->inpcmevt);
    err_exit10:
    x = 2;
    err_exit11:
    y = x;
    while (y)
    {
      y--;
      CloseHandle (dseg->hdrevtbuf[y]);
    }
    DeleteCriticalSection (&(dseg->wavlock));
    HeapFree (heap, 0, q);
    return n;
  }
  ResumeThread (dseg->intthread);
  return 0;
}

/*---------------*/
/*  AUDIO-CLOSE  */
/*---------------*/
                                                                    /* [7207] */
signed short audio_close (struct audio_dseg *dseg)         /* Close Audio-I/O */
{
  signed short n,m;
  unsigned long d;
  unsigned char x;
  MMRESULT rc;
  HANDLE heap;
  int prio;
  BOOL b;

  /* Close Devices */
  n = 0;
  audio__waventer (dseg, &prio);
  dseg->inflags  |= K_AUDIO_FLAGINEND;
  dseg->outflags |= K_AUDIO_FLAGOUTEND;
  m = audio__outstart (dseg);
  audio__wavleave (dseg, prio);
  if (m < 0)
    n = m;
  rc = waveInReset (dseg->inhandle);
  if ((rc != MMSYSERR_NOERROR) && (n >= 0))
    n = audio__errconv (rc);
  rc = waveOutReset (dseg->outhandle);
  if ((rc != MMSYSERR_NOERROR) && (n >= 0))
    n = audio__errconv (rc);
  for (x=0; x<3; x++)
  {
    rc = waveInUnprepareHeader (dseg->inhandle, dseg->inhdrbuf + x, \
                                sizeof (WAVEHDR));
    if ((rc != MMSYSERR_NOERROR) && (n >= 0))
      n = audio__errconv (rc);
  }
  for (x=0; x<3; x++)
  {
    rc = waveOutUnprepareHeader (dseg->outhandle, dseg->outhdrbuf + x, \
                                 sizeof (WAVEHDR));
    if ((rc != MMSYSERR_NOERROR) && (n >= 0))
      n = audio__errconv (rc);
  }
  rc = waveInClose (dseg->inhandle);
  if ((rc != MMSYSERR_NOERROR) && (n >= 0))
    n = audio__errconv (rc);
  rc = waveOutClose (dseg->outhandle);
  if ((rc != MMSYSERR_NOERROR) && (n >= 0))
    n = audio__errconv (rc);
  b = CloseHandle (dseg->intthread);
  if ((!b) && (n >= 0))
    n = E_AUDIO_THREAD;

  /* Free Resources */
  b = CloseHandle (dseg->inpcmevt);
  if ((!b) && (n >= 0))
    n = E_AUDIO_CLOSE;
  b = CloseHandle (dseg->outpcmevt);
  if ((!b) && (n >= 0))
    n = E_AUDIO_CLOSE;
  for (x=0; x<2; x++)
  {
    b = CloseHandle (dseg->hdrevtbuf[x]);
    if ((!b) && (n >= 0))
      n = E_AUDIO_CLOSE;
  }
  DeleteCriticalSection (&(dseg->wavlock));
  d = filter_chunklen[dseg->openopt.samplerate];
  d *= dseg->openopt.winchunks;
  d = (dseg->openopt.bufferlen << 2) + (K_FILTER_CHUNKSIZE_8K << 2) - 4 + \
      (K_FILTER_HISTORYLEN << 2) + (d << 3) + (d << 2);
  memset (dseg->inpcmbuf, 0x00, d);
  heap = GetProcessHeap ();
  b = HeapFree (heap, 0, dseg->inpcmbuf);
  if ((!b) && (n >= 0))
    n = E_AUDIO_FREE;
  return n;
}

/*====*/
                                                                    /* [7208] */
signed short audio_wipe (struct audio_dseg *dseg)        /* Wipe Audio-Buffer */
{
  unsigned long d;
  int prio;

  /* Check Pointer */
  if (dseg == NULL)
    return E_AUDIO_NULLPTR;

  /* Wipe Buffer */
  d = dseg->openopt.bufferlen;
  audio__waventer (dseg, &prio);
  dseg->inflags &= ~K_AUDIO_FLAGINDADA;
  dseg->inpcmwr = 0L;
  dseg->inpcmrd = 0L;
  ResetEvent (dseg->inpcmevt);
  memset (dseg->inpcmbuf, 0x00, (d + K_FILTER_CHUNKSIZE_8K - 1) << 1);
  dseg->outflags &= ~K_AUDIO_FLAGOUTDADA;
  dseg->outpcmwr = 0L;
  dseg->outpcmrd = 0L;
  SetEvent (dseg->outpcmevt);
  memset (dseg->outpcmbuf, 0x00, \
          (d + K_FILTER_HISTORYLEN + K_FILTER_CHUNKSIZE_8K - 1) << 1);
  audio__wavleave (dseg, prio);
  return 0;
}

/*----*/
                                                                    /* [7209] */
signed short audio_restart (struct audio_dseg *dseg)  /* Cancel Audio-Standby */
{
  signed short n;
  int prio;

  /* Check Pointer */
  if (dseg == NULL)
    return E_AUDIO_NULLPTR;

  /* Restart Output */
  audio__waventer (dseg, &prio);
  n = audio__outstart (dseg);
  audio__wavleave (dseg, prio);
  return n;
}

/*--------------*/
/*  AUDIO-READ  */
/*--------------*/
                                                                    /* [7210] */
signed short audio_instop (struct audio_dseg *dseg)       /* Stop Audio-Input */
{
  signed short n;
  MMRESULT rc;
  int prio;

  /* Check Pointer */
  if (dseg == NULL)
    return E_AUDIO_NULLPTR;

  /* Stop Input */
  n = 0;
  audio__waventer (dseg, &prio);
  if (!(dseg->inflags & K_AUDIO_FLAGINSTOP))
  {
    n = 1;
    dseg->inflags |= K_AUDIO_FLAGINSTOP;
    rc = waveInStop (dseg->inhandle);
    if (rc != MMSYSERR_NOERROR)
      n = audio__errconv (rc);
  }
  audio__wavleave (dseg, prio);
  return n;
}

                                                                    /* [7211] */
signed short audio_instart (struct audio_dseg *dseg)     /* Start Audio-Input */
{
  signed short n;
  MMRESULT rc;
  int prio;

  /* Check Pointer */
  if (dseg == NULL)
    return E_AUDIO_NULLPTR;

  /* Start Input */
  n = 1;
  audio__waventer (dseg, &prio);
  if (dseg->inflags & K_AUDIO_FLAGINSTOP)
  {
    n = 0;
    dseg->inflags &= ~K_AUDIO_FLAGINSTOP;
    rc = waveInStart (dseg->inhandle);
    if (rc != MMSYSERR_NOERROR)
      n = audio__errconv (rc);
  }
  audio__wavleave (dseg, prio);
  return n;
}

/*--*/
                                                                    /* [7212] */
signed short audio_inclear (struct audio_dseg *dseg)    /* Clear Input-Buffer */
{
  int prio;

  /* Check Pointer */
  if (dseg == NULL)
    return E_AUDIO_NULLPTR;

  /* Clear Input */
  audio__waventer (dseg, &prio);
  dseg->inflags &= ~K_AUDIO_FLAGINDADA;
  dseg->inpcmwr = 0L;
  dseg->inpcmrd = 0L;
  ResetEvent (dseg->inpcmevt);
  audio__wavleave (dseg, prio);
  return 0;
}
                                                                    /* [7213] */
                                                          /* Get Input-Length */
signed short audio_inlen (struct audio_dseg *dseg, unsigned long *len)
{
  unsigned long a,b;
  int prio;

  /* Check Pointer */
  if ((dseg == NULL) || (len == NULL))
    return E_AUDIO_NULLPTR;

  /* Calc Input-Len */
  audio__waventer (dseg, &prio);
  a = dseg->inpcmwr;
  b = dseg->inpcmrd;
  if ((a < b) || ((a == b) && (dseg->inflags & K_AUDIO_FLAGINDADA)))
    a += dseg->openopt.bufferlen;
  audio__wavleave (dseg, prio);
  *len = a - b;
  return 0;
}

/*----*/                                                            /* [7214] */
                                                            /* Get Input-Data */
signed short audio_inget (struct audio_dseg *dseg, signed short *data, \
                          unsigned long *len, unsigned char wait)
{
  unsigned long a,b,c,d,e,f,g;
  int prio;

  /* Check Pointer */
  if ((dseg == NULL) || (data == NULL) || (len == NULL))
    return E_AUDIO_NULLPTR;

  /* Read Data */
  a = 0L;
  b = *len;
  c = dseg->openopt.bufferlen;
  while (42)
  {
    audio__waventer (dseg, &prio);
    d = dseg->inpcmwr;
    e = dseg->inpcmrd;
    f = d - e;
    if ((d < e) || ((d == e) && (dseg->inflags & K_AUDIO_FLAGINDADA)))
      f += c;
    if (b < f)
      f = b;
    if (f)
    {
      g = f;
      if (e + g > c)
        g = c - e;
      memcpy (data + a, dseg->inpcmbuf + e, g << 1);
      if (f > g)
        memcpy (data + a + g, dseg->inpcmbuf, (f - g) << 1);
      a += f;
      b -= f;
      e += f;
      if (e >= c)
        e -= c;
      if (d == e)
      {
        dseg->inflags &= ~K_AUDIO_FLAGINDADA;
        ResetEvent (dseg->inpcmevt);
      }
      dseg->inpcmrd = e;
    }
    audio__wavleave (dseg, prio);
    if ((!b) || (!wait))
      break;
    WaitForSingleObject (dseg->inpcmevt, INFINITE);
  }
  *len = a;
  return 0;
}

/*---------------*/
/*  AUDIO-WRITE  */
/*---------------*/                                                 /* [7220] */
                                                       /* Clear Output-Buffer */
signed short audio_outclear (struct audio_dseg *dseg, unsigned char soft)
{
  unsigned long a,b,c,d;
  int prio;

  /* Check Pointer */
  if (dseg == NULL)
    return E_AUDIO_NULLPTR;

  /* Clear Output */
  a = dseg->openopt.bufferlen;
  audio__waventer (dseg, &prio);
  b = dseg->outpcmwr;
  c = dseg->outpcmrd;
  d = b - c;
  if ((b < c) || ((b == c) && (dseg->outflags & K_AUDIO_FLAGOUTDADA)))
    d += a;
  if (!soft)
    d = 0L;
  else if (d > K_FILTER_CHUNKSIZE_8K)
    d = K_FILTER_CHUNKSIZE_8K;
  b = c + d;
  if (b >= a)
    b -= a;
  dseg->outpcmwr = b;
  if (!d)
    dseg->outflags &= ~K_AUDIO_FLAGOUTDADA;
  if (d <= a - dseg->openopt.triggerlvl)
    SetEvent (dseg->outpcmevt);
  audio__wavleave (dseg, prio);
  return 0;
}
                                                                    /* [7221] */
                                                         /* Get Output-Length */
signed short audio_outlen (struct audio_dseg *dseg, unsigned long *len)
{
  unsigned long a,b;
  int prio;

  /* Check Pointer */
  if ((dseg == NULL) || (len == NULL))
    return E_AUDIO_NULLPTR;

  /* Calc Output-Len */
  audio__waventer (dseg, &prio);
  a = dseg->outpcmwr;
  b = dseg->outpcmrd;
  if ((a < b) || ((a == b) && (dseg->outflags & K_AUDIO_FLAGOUTDADA)))
    a += dseg->openopt.bufferlen;
  audio__wavleave (dseg, prio);
  *len = a - b;
  return 0;
}

/*----*/                                                            /* [7222] */
                                                           /* Put Output-Data */
signed short audio_output (struct audio_dseg *dseg, const signed short *data, \
                           unsigned long *len, unsigned char wait)
{
  unsigned long a,b,c,d,e,f,g,h;
  const signed short *q;
  signed short *p,n,m;
  int prio;

  /* Check Pointer */
  if ((dseg == NULL) || (data == NULL) || (len == NULL))
    return E_AUDIO_NULLPTR;

  /* Write Data */
  a = 0L;
  b = *len;
  c = dseg->openopt.bufferlen;
  n = 0;
  while (42)
  {
    audio__waventer (dseg, &prio);
    d = dseg->outpcmwr;
    e = dseg->outpcmrd;
    f = e - d;
    if ((d > e) || ((d == e) && !(dseg->outflags & K_AUDIO_FLAGOUTDADA)))
      f += c;
    if (b < f)
      f = b;
    if (f)
    {
      while (f)
      {
        g = f;
        if (d + g > c)
          g = c - d;
        memcpy (dseg->outpcmbuf + K_FILTER_HISTORYLEN + d, data + a, g << 1);
        if (d + g > c - K_FILTER_HISTORYLEN)
        {
          h = d;
          if (h < c - K_FILTER_HISTORYLEN)
            h = c - K_FILTER_HISTORYLEN;
          p = dseg->outpcmbuf + K_FILTER_HISTORYLEN + h - c;
          q = dseg->outpcmbuf + K_FILTER_HISTORYLEN + h;
          while (h < d + g)
          {
            *p++ = *q++;
            h++;
          }
        }
        a += g;
        b -= g;
        d += g;
        if (d >= c)
          d = 0L;
        f -= g;
      }
      dseg->outflags |= K_AUDIO_FLAGOUTDADA;
      dseg->outpcmwr = d;
      if (d == e)
        ResetEvent (dseg->outpcmevt);
      m = audio__outstart (dseg);
      if (m < 0)
        n = m;
    }
    audio__wavleave (dseg, prio);
    if ((n < 0) || (!b) || (!wait))
      break;
    WaitForSingleObject (dseg->outpcmevt, INFINITE);
  }
  *len = a;
  return n;
}

/*====*/
                                                               /* Enter Audio */
static void audio__waventer (struct audio_dseg *dseg, int *prio)
{
  HANDLE thnd;

  /* Enter Audio-Section */
  thnd = GetCurrentThread ();
  *prio = GetThreadPriority (thnd);
#ifndef _WIN32_WCE
  SetThreadPriority (thnd, K_AUDIO_INTPRIO);
#else
  CeSetThreadPriority (thnd, K_AUDIO_INTPRIO);
#endif
  EnterCriticalSection (&(dseg->wavlock));
}

                                                               /* Leave Audio */
static void audio__wavleave (struct audio_dseg *dseg, int prio)
{
  HANDLE thnd;

  /* Leave Audio-Section */
  LeaveCriticalSection (&(dseg->wavlock));
  thnd = GetCurrentThread ();
  SetThreadPriority (thnd, prio);
}

/*----*/
                                                            /* Restart Output */
static signed short audio__outstart (struct audio_dseg *dseg)
{
  signed short n;
  MMRESULT rc;

  /* Cancel Standby */
  n = 1;
  if (dseg->outflags & K_AUDIO_FLAGOUTSTOP)
  {
    n = 0;
    dseg->outflags &= ~K_AUDIO_FLAGOUTSTOP;
    dseg->outidle = 0L;
    rc = waveOutRestart (dseg->outhandle);
    if (rc != MMSYSERR_NOERROR)
      n = audio__errconv (rc);
  }
  return n;
}

/*-----------------*/
/*  AUDIO-CONTROL  */
/*-----------------*/                                               /* [7230] */
                                                           /* Find Input-Port */
signed short audio_firstinport (const struct audio_dseg *dseg, \
                                unsigned char *port)
{
#ifndef _WIN32_WCE
  unsigned char y;
#endif
  unsigned char x;
  signed short n;

  /* Check Pointer */
  if ((dseg == NULL) || (port == NULL))
    return E_AUDIO_NULLPTR;

  /* First In-Chan */
#ifndef _WIN32_WCE
  n = 0;
  if (dseg->mixin_info != NULL)
  {
    y = dseg->hwconfig.devin_mixmap;
    for (x=1; x<=K_AUDIO_MAXPORTIN; x++)
    {
      if (y & 0x01)
      {
        n = 1;
        break;
      }
      y >>= 1;
    }
  }
#else
  n = 1;
  x = 1;
#endif
  if (n)
    *port = x;
  else
    *port = 0;
  return n;
}
                                                                    /* [7231] */
                                                          /* Check Input-Port */
signed short audio_chkinport (const struct audio_dseg *dseg, \
                              unsigned char *port)
{
#ifndef _WIN32_WCE
  unsigned char x;
#endif
  signed short n;

  /* Check Pointer */
  if ((dseg == NULL) || (port == NULL))
    return E_AUDIO_NULLPTR;

  /* Check In-Chan */
  n = 0;
#ifndef _WIN32_WCE
  if (*port == 0)
    n = 1;
  else if ((*port <= K_AUDIO_MAXPORTIN) && (dseg->mixin_info != NULL))
  {
    x = 0x01;
    x <<= (*port - 1);
    if (dseg->hwconfig.devin_mixmap & x)
      n = 1;
  }
#else
  if (*port <= 1)
    n = 1;
#endif
  if (n == 0)
    *port = 0;
  return n;
}

/*--*/                                                              /* [7232] */
                                                          /* Get Input-Source */
signed short audio_getinmux (const struct audio_dseg *dseg, \
                             unsigned char *port)
{
  /* Check Pointer */
  if ((dseg == NULL) || (port == NULL))
    return E_AUDIO_NULLPTR;

  /* Get In-Chan */
  *port = dseg->mixin_sel;
  return 0;
}
                                                                    /* [7233] */
                                                          /* Set Input-Source */
signed short audio_setinmux (struct audio_dseg *dseg, \
                             unsigned char port)
{
#ifndef _WIN32_WCE
  unsigned char x,y,z;
  unsigned short v;
  signed short m;
#endif
  signed short n;

  /* Check Pointer */
  if (dseg == NULL)
    return E_AUDIO_NULLPTR;

  /* Set In-Chan */
  n = 0;  /* port==0 is "mute" */
  audio_chkinport (dseg, &port);
  EnterCriticalSection (&(dseg->mixlock));
#ifndef _WIN32_WCE
  if (dseg->mixin_info != NULL)
  {
    n = audio__setmixlvl (0x0000, 0, &(dseg->mixin_info->info[0][0]), \
                          dseg->hwconfig.devin_mixdev);
    y = dseg->hwconfig.devin_mixmap;
    for (x=0; x<K_AUDIO_MAXPORTIN; x++)
    {
      if (y & 0x01)
      {
        z = dseg->hwconfig.devin_mixin[x];
        m = audio__setmixlvl (0x0000, 0, &(dseg->mixin_info->info[z+1][0]), \
                              dseg->hwconfig.devin_mixdev);
        if (n >= 0)
          n = m;
      }
      y >>= 1;
    }
    if (port)
    {
      v = dseg->hwconfig.devin_mixlvl[port-1];
      if (v)
      {
        m = audio__setinpath (dseg, port, v);
        if (n >= 0)
          n = m;
      }
    }
  }
#endif
  dseg->mixin_sel = port;
  LeaveCriticalSection (&(dseg->mixlock));
  return n;
}

/*--*/                                                              /* [7234] */
                                                          /* Get Input-Volume */
signed short audio_getinvol (const struct audio_dseg *dseg, \
                             unsigned char port, unsigned short *vol)
{
  /* Check Pointer */
  if ((dseg == NULL) || (vol == NULL))
    return E_AUDIO_NULLPTR;

  /* Get In-Vol */
  audio__transinport (dseg, &port);  /* port==0 is "current" */
  if (port)
    *vol = dseg->hwconfig.devin_mixlvl[port-1];
  else
    *vol = 0x0000;
  return 0;
}
                                                                    /* [7235] */
                                                          /* Set Input-Volume */
signed short audio_setinvol (struct audio_dseg *dseg, \
                             unsigned char port, unsigned short vol)
{
  signed short n;

  /* Check Pointer */
  if (dseg == NULL)
    return E_AUDIO_NULLPTR;

  /* Set In-Vol */
  n = 0;  /* port==0 is "current" */
  audio__transinport (dseg, &port);
  if (port)
  {
    EnterCriticalSection (&(dseg->mixlock));
    dseg->hwconfig.devin_mixlvl[port-1] = vol;
#ifndef _WIN32_WCE
    if (port == dseg->mixin_sel)
      n = audio__setinpath (dseg, port, vol);
#endif
    LeaveCriticalSection (&(dseg->mixlock));
  }
  return n;
}

/*----*/                                                            /* [7236] */
                                                          /* Find Output-Port */
signed short audio_firstoutport (const struct audio_dseg *dseg, \
                                 unsigned char *port)
{
  unsigned char x;
  signed short n;

  /* Check Pointer */
  if ((dseg == NULL) || (port == NULL))
    return E_AUDIO_NULLPTR;

  /* First Out-Chan */
#ifndef _WIN32_WCE
  n = 0;
  for (x=1; x<=K_AUDIO_MAXPORTOUT; x++)
  {
    if (dseg->mixout_info[x-1] != NULL)
    {
      n = 1;
      break;
    }
  }
#else
  n = 1;
  x = 1;
#endif
  if (n)
    *port = x;
  else
    *port = 0;
  return n;
}
                                                                    /* [7237] */
                                                         /* Check Output-Port */
signed short audio_chkoutport (const struct audio_dseg *dseg, \
                               unsigned char *port)
{
  signed short n;

  /* Check Pointer */
  if ((dseg == NULL) || (port == NULL))
    return E_AUDIO_NULLPTR;

  /* Check Out-Chan */
  n = 0;
#ifndef _WIN32_WCE
  if ((*port > 0) && (*port <= K_AUDIO_MAXPORTOUT))
    if (dseg->mixout_info[*port-1] != NULL)
      n = 1;
#else
  if (*port == 1)
    n = 1;
#endif
  if (n == 0)
    *port = 0;
  return n;
}

/*--*/                                                              /* [7238] */
                                                         /* Get Output-Volume */
signed short audio_getoutvol (struct audio_dseg *dseg, \
                              unsigned char port, unsigned short *vol)
{
#ifdef _WIN32_WCE
  unsigned long d;
  MMRESULT rc;
#endif
  signed short n;

  /* Check Pointer */
  if ((dseg == NULL) || (vol == NULL))
    return E_AUDIO_NULLPTR;

  /* Get Out-Vol */
  n = 0;  /* port==0 is "first" */
  audio__transoutport (dseg, &port);
  if (port)
  {
#ifdef _WIN32_WCE
    rc = waveOutGetVolume ((HWAVEOUT)(dseg->hwconfig.devout_wave), &d);
    if (rc != MMSYSERR_NOERROR)
      n = audio__errconv (rc);
    else
      dseg->hwconfig.devout_mixlvl[port-1] = (unsigned short)d;
#endif
    *vol = dseg->hwconfig.devout_mixlvl[port-1];
  }
  else
    *vol = 0x0000;
  return n;
}
                                                                    /* [7239] */
                                                         /* Set Output-Volume */
signed short audio_setoutvol (struct audio_dseg *dseg, \
                              unsigned char port, unsigned short vol)
{
#ifdef _WIN32_WCE
  unsigned long d;
  MMRESULT rc;
#endif
  signed short n;

  /* Check Pointer */
  if (dseg == NULL)
    return E_AUDIO_NULLPTR;

  /* Set Out-Vol */
  n = 0;  /* port==0 is "first" */
  audio__transoutport (dseg, &port);
  if (port)
  {
    EnterCriticalSection (&(dseg->mixlock));
    dseg->hwconfig.devout_mixlvl[port-1] = vol;
#ifndef _WIN32_WCE
    n = audio__setoutpath (dseg, port, vol);
#else
    d = vol;
    d = d << 16;
    d = d | vol;
    rc = waveOutSetVolume ((HWAVEOUT)(dseg->hwconfig.devout_wave), d);
    if (rc != MMSYSERR_NOERROR)
      n = audio__errconv (rc);
#endif
    LeaveCriticalSection (&(dseg->mixlock));
  }
  return n;
}

/*====*/
                                                      /* Translate Input-Port */
static void audio__transinport (const struct audio_dseg *dseg, \
                                unsigned char *port)
{
  if (*port == 0)  /* port==0 is "current" */
    *port = dseg->mixin_sel;
  else
    audio_chkinport (dseg, port);
}

/*--*/

#ifndef _WIN32_WCE
                                                            /* Set Input-Path */
static signed short audio__setinpath (const struct audio_dseg *dseg, \
                                      unsigned char port, unsigned short vol)
{
  signed short n,m;
  unsigned short v;
  unsigned char x;

  x = dseg->hwconfig.devin_mixin[port-1];  /* port != 0 */
  n = audio__setmixlvl (vol, 0, &(dseg->mixin_info->info[x+1][0]), \
                        dseg->hwconfig.devin_mixdev);
  v = vol ? 0xFFFF : 0x0000;
  m = audio__setmixlvl (v, x, &(dseg->mixin_info->info[0][0]), \
                        dseg->hwconfig.devin_mixdev);
  if (n >= 0)
    n = m;
  return n;
}
#endif

/*----*/
                                                     /* Translate Output-Port */
static void audio__transoutport (const struct audio_dseg *dseg, \
                                 unsigned char *port)
{
  if (*port == 0)  /* port==0 is "first" */
    audio_firstoutport (dseg, port);
  else
    audio_chkoutport (dseg, port);
}

/*--*/

#ifndef _WIN32_WCE
                                                           /* Set Output-Path */
static signed short audio__setoutpath (const struct audio_dseg *dseg, \
                                       unsigned char port, unsigned short vol)
{
  signed short n,m;
  unsigned char x;

  x = dseg->hwconfig.devout_mixin[port-1];  /* port != 0 */
  n = audio__setmixlvl (vol, x, &(dseg->mixout_info[port-1]->info[0][0]), \
                        dseg->hwconfig.devout_mixdev[port-1]);
  m = audio__setmixlvl (0xFFFF, 0, &(dseg->mixout_info[port-1]->info[x+1][0]), \
                        dseg->hwconfig.devout_mixdev[port-1]);
  if (n >= 0)
    n = m;
  return n;
}
#endif

/*============================================================================*/

/*--------------*/
/*  Int-Server  */
/*--------------*/
                                                         /* Audio Rx-Callback */
static void CALLBACK audio__callback_in (HWAVEIN hwi, UINT msg, DWORD p0, \
                                         DWORD p1, DWORD p2)
{
  struct audio_dseg *dseg;

  /* Rx Callback */
  dseg = (struct audio_dseg *)p0;
  if (msg == WIM_DATA)
  {
    dseg->inhdrint = (WAVEHDR *)p1;
    SetEvent (dseg->hdrevtbuf[1]);
  }
  else if (msg == WIM_CLOSE)
  {
    dseg->inhdrint = NULL;
    SetEvent (dseg->hdrevtbuf[1]);
  }
}

/*----*/
                                                         /* Audio Tx-Callback */
static void CALLBACK audio__callback_out (HWAVEOUT hwo, UINT msg, DWORD p0, \
                                          DWORD p1, DWORD p2)
{
  struct audio_dseg *dseg;

  /* Tx Callback */
  dseg = (struct audio_dseg *)p0;
  if (msg == WOM_DONE)
  {
    dseg->outhdrint = (WAVEHDR *)p1;
    SetEvent (dseg->hdrevtbuf[0]);
  }
  else if (msg == WOM_CLOSE)
  {
    dseg->outhdrint = NULL;
    SetEvent (dseg->hdrevtbuf[0]);
  }
}

/*====*/

static DWORD WINAPI audio__intthread (LPVOID ptr)         /* Audio Int-Thread */
{
  struct audio_dseg *dseg;
  const signed short *q;
  unsigned char x,y,z;
  signed short *p,n;
  unsigned short w;
  WAVEHDR *whdr;
  MMRESULT rc;
  DWORD d;

  /* Audio Soft-Int */
  dseg = ptr;
  while (42)
  {
    d = WaitForMultipleObjects (2, dseg->hdrevtbuf, FALSE, INFINITE);
    if (d == WAIT_OBJECT_0)
    {
      /* Transmit Interrupt */
      ResetEvent (dseg->hdrevtbuf[0]);
      whdr = (void *)(dseg->outhdrint);
      if (whdr != NULL)
      {
        y = whdr - dseg->outhdrbuf;
        EnterCriticalSection (&(dseg->wavlock));
        x = dseg->outhdrpos;
        while (x != y)
        {
          if (!(dseg->outflags & K_AUDIO_FLAGOUTEND))
          {
            /* Produce output chunk */
            whdr = dseg->outhdrbuf + x;
            audio__txchunk (dseg, (signed short *)(whdr->lpData), \
                            (unsigned short)(whdr->dwBufferLength >> 1));
            /* Insert full chunk */
            rc = waveOutWrite (dseg->outhandle, whdr, sizeof (WAVEHDR));
            if (rc != MMSYSERR_NOERROR)
              goto err_exit;
          }
          x++;
          if (x > 2)
            x = 0;
        }
        dseg->outhdrpos = x;
        LeaveCriticalSection (&(dseg->wavlock));
      }
      else
        break;
    }
    else if (d == (WAIT_OBJECT_0 + 1))
    {
      /* Receive Interrupt */
      ResetEvent (dseg->hdrevtbuf[1]);
      whdr = (void *)(dseg->inhdrint);
      if (whdr != NULL)
      {
        y = whdr - dseg->inhdrbuf;
        EnterCriticalSection (&(dseg->wavlock));
        x = dseg->inhdrpos;
        while (x != y)
        {
          if (!(dseg->inflags & K_AUDIO_FLAGINEND))
          {
            /* Insert empty chunk */
            whdr = dseg->inhdrbuf + x;
            rc = waveInAddBuffer (dseg->inhandle, whdr, sizeof (WAVEHDR));
            if (rc != MMSYSERR_NOERROR)
            {
              err_exit:
              n = audio__errconv (rc);
              LeaveCriticalSection (&(dseg->wavlock));
              return n;
            }
          }
          /* Process input chunk */
          x++;
          if (x > 2)
            x = 0;
          whdr = dseg->inhdrbuf + x;
          if (whdr->dwBytesRecorded == whdr->dwBufferLength)
          {
            w = (unsigned short)(whdr->dwBufferLength >> 1);
            if (x == 2)
            {
              p = dseg->inhdrpcm;
              q = (const signed short *)(whdr->lpData) + \
                  w - K_FILTER_HISTORYLEN;
              for (z=0; z<K_FILTER_HISTORYLEN; z++)
                *p++ = *q++;
            }
            audio__rxchunk (dseg, (const signed short *)(whdr->lpData), w);
          }
        }
        dseg->inhdrpos = x;
        LeaveCriticalSection (&(dseg->wavlock));
      }
    }
  }
  return 0;
}

/*====*/
                                                          /* Audio Rx-Service */
static void audio__rxchunk (struct audio_dseg *dseg, const signed short *data, \
                            unsigned short len)
{
  unsigned long a,b,c;
  unsigned char f,s;
  unsigned short w;

  /* Setup variables */
  a = dseg->openopt.bufferlen;
  b = dseg->inpcmwr;
  c = dseg->inpcmrd;
  if ((c < b) || !(dseg->inflags & K_AUDIO_FLAGINDADA))
    c += a;
  c -= b;
  f = 0;
  s = dseg->openopt.samplerate;
  w = filter_chunklen[s];

  /* Transfer data */
  while (len >= w)
  {
    f = 1;
    filter_resample_xto8 (data, dseg->inpcmbuf + b, s);
    b += K_FILTER_CHUNKSIZE_8K;
    if (b > a)
      memcpy (dseg->inpcmbuf, dseg->inpcmbuf + a, (b - a) << 1);
    if (b >= a)
      b -= a;
    if (c >= K_FILTER_CHUNKSIZE_8K)
      c -= K_FILTER_CHUNKSIZE_8K;
    else
    {
      c = 0L;
      f = 2;
    }
    data += w;
    len -= w;
  }

  /* Update pointer */
  if (f)
  {
    dseg->inflags |= K_AUDIO_FLAGINDADA;
    dseg->inpcmwr = b;
    if (f > 1)
    {
      dseg->inflags |= K_AUDIO_FLAGINOVR;
      dseg->inpcmrd = b;
    }
    if (c <= a - dseg->openopt.triggerlvl)
      SetEvent (dseg->inpcmevt);
  }
}

/*----*/
                                                          /* Audio Tx-Service */
static void audio__txchunk (struct audio_dseg *dseg, signed short *data, \
                            unsigned short len)
{
  unsigned long a,b,c,d,e;
  signed short *p,*q,n;
  unsigned short w;
  unsigned char s;

  /* Transfer data */
  s = dseg->openopt.samplerate;
  w = filter_chunklen[s];
  while (len >= w)
  {
    if (dseg->outstate)
    {
      /* Voice sound */
      dseg->outidle = 0L;
      if (!(dseg->outflags & K_AUDIO_FLAGOUTDADA))
      {
        dseg->outstate = K_AUDIO_OSTATE_SYNTH;
        n = sound_deliver (dseg->openopt.sound, &p);
        if (!n)
          dseg->outidle = 1L;
        audio__rampup (p);
      }
      else
      {
        /* Load variables */
        a = dseg->openopt.bufferlen;
        b = dseg->outpcmwr;
        c = dseg->outpcmrd;
        d = b - c;
        if (b <= c)
          d += a;
        q = dseg->outpcmbuf + K_FILTER_HISTORYLEN;
        /* Fillup chunksize */
        if (d < K_FILTER_CHUNKSIZE_8K)
        {
          p = q + b - 1;
          n = *p++;
          while (d < K_FILTER_CHUNKSIZE_8K)
          {
            *p++ = n;
            b++;
            d++;
            if (b >= a)
            {
              b = 0L;
              p = q;
            }
          }
        }
        /* Modify chunk */
        p = q + c;
        e = c + K_FILTER_CHUNKSIZE_8K;
        if (e > a)
          memcpy (q + a, q, (e - a) << 1);
        if (d <= K_FILTER_CHUNKSIZE_8K)
        {
          if (dseg->outstate == K_AUDIO_OSTATE_VOICEDOWN)
            memset (p, 0x00, K_FILTER_CHUNKSIZE_8K << 1);
          else
          {
            audio__rampdown (p);
            dseg->outstate = K_AUDIO_OSTATE_VOICEDOWN;
          }
        }
        else if (dseg->outstate == K_AUDIO_OSTATE_VOICEDOWN)
        {
          audio__rampup (p);
          dseg->outstate = K_AUDIO_OSTATE_VOICEUP;
        }
        if (e > a - K_FILTER_HISTORYLEN)
        {
          if (c < a - K_FILTER_HISTORYLEN)
            c = a - K_FILTER_HISTORYLEN;
          c = c - a + K_FILTER_HISTORYLEN;
          memcpy (dseg->outpcmbuf + c, dseg->outpcmbuf + a + c, \
                  (e - a - c + K_FILTER_HISTORYLEN) << 1);
        }
        c = e;
        if (c >= a)
          c -= a;
        d -= K_FILTER_CHUNKSIZE_8K;
        /* Store variables */
        if (!d)
          dseg->outflags &= ~K_AUDIO_FLAGOUTDADA;
        dseg->outpcmwr = b;
        dseg->outpcmrd = c;
        if (d <= a - dseg->openopt.triggerlvl)
          SetEvent (dseg->outpcmevt);
      }
    }
    else
    {
      /* Synthi sound */
      n = sound_deliver (dseg->openopt.sound, &p);
      if (dseg->outflags & K_AUDIO_FLAGOUTDADA)
      {
        audio__rampdown (p);
        dseg->outstate = K_AUDIO_OSTATE_VOICEDOWN;
        dseg->outidle = 0L;
      }
      else if (n > 0)
        dseg->outidle = 0L;
      else if (dseg->outidle < 0xFFFFFFFFL)
      {
        dseg->outidle++;
        if (dseg->outidle == K_AUDIO_OUTIDLETIME)
        {
          dseg->outflags |= K_AUDIO_FLAGOUTSTOP;
          waveOutPause (dseg->outhandle);
        }
      }
    }
    filter_resample_8tox (p, data, s);
    data += w;
    len -= w;
  }
}

/*====*/

static void audio__rampup (signed short *data)              /* Ramp Volume Up */
{
  const unsigned short *p;
  unsigned char x;
  signed long a,b;

  /* Soft-On */
  p = audio__ramptbl;
  for (x=0; x<K_FILTER_CHUNKSIZE_8K; x++)
  {
    a = 0x0000FFFFL - *p++;
    b = *data;
    a = (a * b) + 0x00008000L;
    *data++ = (signed short)(a >> 16);
  }
}


static void audio__rampdown (signed short *data)          /* Ramp Volume Down */
{
  const unsigned short *p;
  unsigned char x;
  signed long a,b;

  /* Soft-Off */
  p = audio__ramptbl;
  for (x=0; x<K_FILTER_CHUNKSIZE_8K; x++)
  {
    a = *p++;
    b = *data;
    a = (a * b) + 0x00008000L;
    *data++ = (signed short)(a >> 16);
  }
}

/*----------------------------------------------------------------------------*/

/*------------------*/
/*  Mixer-Controls  */
/*------------------*/

#ifndef _WIN32_WCE
                                                          /* Get Control-List */
static void audio__getmixinfo (struct audio_mixoutinfo *info,                  \
                               unsigned char mixdev,                           \
                               unsigned char mixout)
{
  MIXERCONTROL ctrl[K_AUDIO_MAXMIXCTRL];
  unsigned char i,c,a,b,s,e,x,y;
  MIXERLINECONTROLS ctrlhdr;
  MIXERLINE line;
  MMRESULT rc;

  line.cbStruct      = sizeof (MIXERLINE);
  line.dwDestination = mixout;
  line.dwSource      = 0;
  rc = mixerGetLineInfo ((HMIXEROBJ)mixdev, &line, \
                           MIXER_OBJECTF_MIXER     \
                         | MIXER_GETLINEINFOF_DESTINATION);
  if (rc != MMSYSERR_NOERROR)
  {
    for (x=0; x<=K_AUDIO_MAXMIXIN; x++)
      for (y=0; y<K_AUDIO_MAXMIXCTRL; y++)
        info->info[x][y].valid = K_AUDIO_DEVICE_ERROR;
    return;
  }

  if (line.cConnections > 0xFF)
   i = 0xFF;
  else
   i = (unsigned char)(line.cConnections);
  e = 0;
  for (x=0; x<=K_AUDIO_MAXMIXIN; x++)
  {
    if (x <= i)
    {
      if (e == 0)
      {
        if (line.cControls > 0xFF)
          c = 0xFF;
        else
          c = (unsigned char)(line.cControls);
        if (c)
        {
          if (c > K_AUDIO_MAXMIXCTRL)
            c = K_AUDIO_MAXMIXCTRL;
          ctrlhdr.cbStruct      = sizeof (MIXERLINECONTROLS);
          ctrlhdr.dwLineID      = line.dwLineID;
          ctrlhdr.dwControlID   = 0;
          ctrlhdr.dwControlType = 0;
          ctrlhdr.cControls     = c;
          ctrlhdr.cbmxctrl      = sizeof (MIXERCONTROL);
          ctrlhdr.pamxctrl      = ctrl;
          rc = mixerGetLineControls ((HMIXEROBJ)mixdev, &ctrlhdr, \
                                   MIXER_OBJECTF_MIXER            \
                                 | MIXER_GETLINECONTROLSF_ALL);
        }
        for (y=0; y<K_AUDIO_MAXMIXCTRL; y++)
        {
          if (y < c)
          {
            if (rc == MMSYSERR_NOERROR)
            {
              if (ctrl[y].fdwControl & MIXERCONTROL_CONTROLF_UNIFORM)
                a = 1;
              else
              {
                if (line.cChannels > 0xFF)
                  a = 0xFF;
                else
                  a = (unsigned char)(line.cChannels);
              }
              if (ctrl[y].fdwControl & MIXERCONTROL_CONTROLF_MULTIPLE)
              {
                if (ctrl[y].cMultipleItems > 0xFF)
                  b = 0xFF;
                else
                  b = (unsigned char)(ctrl[y].cMultipleItems);
              }
              else
                b = 1;
              switch (ctrl[y].dwControlType & MIXERCONTROL_CT_UNITS_MASK)
              {
                case MIXERCONTROL_CT_UNITS_BOOLEAN:
                  s = sizeof (MIXERCONTROLDETAILS_BOOLEAN);
                  break;
                case MIXERCONTROL_CT_UNITS_SIGNED:
                case MIXERCONTROL_CT_UNITS_DECIBELS:
                  s = sizeof (MIXERCONTROLDETAILS_SIGNED);
                  break;
                case MIXERCONTROL_CT_UNITS_UNSIGNED:
                case MIXERCONTROL_CT_UNITS_PERCENT:
                  s = sizeof (MIXERCONTROLDETAILS_UNSIGNED);
                  break;
                case MIXERCONTROL_CT_UNITS_CUSTOM:
                  a = 1;
                  b = 1;
                  if (ctrl[y].Metrics.cbCustomData > 0xFF)
                    s = 0xFF;
                  else
                    s = (unsigned char)(ctrl[y].Metrics.cbCustomData);
                  break;
                default:
                  s = 0xFF;
              }
              info->info[x][y].valid = K_AUDIO_DEVICE_OK;
              info->info[x][y].dchan = a;
              info->info[x][y].ditem = b;
              info->info[x][y].dsize = s;
              memcpy (&(info->info[x][y].ctrlinfo), \
                      &(ctrl[y]), sizeof (MIXERCONTROL));
            }
            else
              info->info[x][y].valid = K_AUDIO_DEVICE_ERROR;
          }
          else
            info->info[x][y].valid = K_AUDIO_DEVICE_NOHW;
        }
      }
      else
      {
        for (y=0; y<K_AUDIO_MAXMIXCTRL; y++)
          info->info[x][y].valid = K_AUDIO_DEVICE_ERROR;
        e = 0;
      }
    }
    else
      for (y=0; y<K_AUDIO_MAXMIXCTRL; y++)
        info->info[x][y].valid = K_AUDIO_DEVICE_NOHW;
    if (x < i)
    {
      line.cbStruct      = sizeof (MIXERLINE);
      line.dwDestination = mixout;
      line.dwSource      = x;
      rc = mixerGetLineInfo ((HMIXEROBJ)mixdev, &line, \
                             MIXER_OBJECTF_MIXER | MIXER_GETLINEINFOF_SOURCE);
      if (rc != MMSYSERR_NOERROR)
        e = 1;
    }
  }
}

/*----*/
                                                  /* Get Control-Settings-Len */
static unsigned long audio__getmixdlen (const struct audio_mixoutinfo *info)
{
  const struct audio_mixctrlinfo *ctrlinfo;
  unsigned char x,y;
  unsigned long len;
  signed short n;

  len = 0L;
  for (x=0; x<=K_AUDIO_MAXMIXIN; x++)
  {
    ctrlinfo = &(info->info[x][0]);
    for (y=0; y<K_AUDIO_MAXMIXCTRL; y++)
    {
      if (ctrlinfo->valid == K_AUDIO_DEVICE_NOHW)
        break;
      n = audio___chkmixinfo (ctrlinfo);
      if (n > 0)
        len += (1 + n);
      ctrlinfo++;
    }
  }
  return len;
}

/*----------------*/
/*  Mixer-Access  */
/*----------------*/
                                                     /* Init Control-Settings */
static signed short audio__initmixout (const struct audio_mixoutinfo *info,    \
                                       unsigned char mixdev)
{
  const struct audio_mixctrlinfo *ctrlinfo;
  union audio_mixctrldata data;
  unsigned char x,y;
  signed short n,m;
  DWORD u;
  LONG s;

  n = 0;
  for (x=0; x<=K_AUDIO_MAXMIXIN; x++)
  {
    ctrlinfo = &(info->info[x][0]);
    for (y=0; y<K_AUDIO_MAXMIXCTRL; y++)
    {
      if (ctrlinfo->valid == K_AUDIO_DEVICE_NOHW)
        break;
      else if (ctrlinfo->valid == K_AUDIO_DEVICE_OK)
      {
        switch (ctrlinfo->ctrlinfo.dwControlType)
        {
          case MIXERCONTROL_CONTROLTYPE_STEREOENH:
            m = audio___setmixpar_b (&data, 0, ctrlinfo);
            goto set_val;
          case MIXERCONTROL_CONTROLTYPE_MUTE:   /**/
          case MIXERCONTROL_CONTROLTYPE_ONOFF:
          case MIXERCONTROL_CONTROLTYPE_LOUDNESS:
          case MIXERCONTROL_CONTROLTYPE_MONO:
            m = audio___setmixpar_b (&data, 1, ctrlinfo);
            goto set_val;
          case MIXERCONTROL_CONTROLTYPE_MUX:
            m = audio___setmixpar_m (&data, 0, ctrlinfo);
            goto set_val;
          case MIXERCONTROL_CONTROLTYPE_VOLUME: /**/
          case MIXERCONTROL_CONTROLTYPE_BASS:
          case MIXERCONTROL_CONTROLTYPE_TREBLE:
          case MIXERCONTROL_CONTROLTYPE_EQUALIZER:
            u = ctrlinfo->ctrlinfo.Bounds.dwMinimum;
            m = audio___setmixpar_u (&data, u, ctrlinfo);
            goto set_val;
          case MIXERCONTROL_CONTROLTYPE_PAN:
          case MIXERCONTROL_CONTROLTYPE_QSOUNDPAN:
            if (ctrlinfo->ctrlinfo.Bounds.lMinimum > 0)
              s = ctrlinfo->ctrlinfo.Bounds.lMinimum;
            else if (ctrlinfo->ctrlinfo.Bounds.lMaximum < 0)
              s = ctrlinfo->ctrlinfo.Bounds.lMaximum;
            else
              s = 0;
            m = audio___setmixpar_s (&data, s, ctrlinfo);
            set_val:
            if (m >= 0)
              m = audio___setmixdata (&data, ctrlinfo, mixdev);
            if ((m < 0) && (n >= 0))
              n = m;
            break;
        }
      }
      ctrlinfo++;
    }
  }
  return n;
}

/*----*/
                                                     /* Save Control-Settings */
static signed short audio__savemixstate (void *state,                          \
                                         const struct audio_mixoutinfo *info,  \
                                         unsigned char mixdev)
{
  const struct audio_mixctrlinfo *ctrlinfo;
  union audio_mixctrldata data;
  unsigned char x,y,*p;
  signed short n,m,s;

  n = 0;
  p = state;
  for (x=0; x<=K_AUDIO_MAXMIXIN; x++)
  {
    ctrlinfo = &(info->info[x][0]);
    for (y=0; y<K_AUDIO_MAXMIXCTRL; y++)
    {
      if (ctrlinfo->valid == K_AUDIO_DEVICE_NOHW)
        break;
      s = audio___chkmixinfo (ctrlinfo);
      if (s > 0)
      {
        m = audio___getmixdata (&data, ctrlinfo, mixdev);
        if (m >= 0)
        {
          *p++ = 0x01;
          memcpy (p, &data, s);
        }
        else
        {
          *p++ = 0x00;
          if (n >= 0)
            n = m;
        }
        p += s;
      }
      ctrlinfo++;
    }
  }
  return n;
}

                                                     /* Load Control-Settings */
static signed short audio__loadmixstate (const void *state,                    \
                                         const struct audio_mixoutinfo *info,  \
                                         unsigned char mixdev)
{
  const struct audio_mixctrlinfo *ctrlinfo;
  union audio_mixctrldata data;
  const unsigned char *p;
  signed short n,m,s;
  unsigned char x,y;

  n = 0;
  p = state;
  for (x=0; x<=K_AUDIO_MAXMIXIN; x++)
  {
    ctrlinfo = &(info->info[x][0]);
    for (y=0; y<K_AUDIO_MAXMIXCTRL; y++)
    {
      if (ctrlinfo->valid == K_AUDIO_DEVICE_NOHW)
        break;
      s = audio___chkmixinfo (ctrlinfo);
      if (s > 0)
      {
        if (*p++)
        {
          memcpy (&data, p, s);
          m = audio___setmixdata (&data, ctrlinfo, mixdev);
          if ((m < 0) && (n >= 0))
            n = m;
        }
        p += s;
      }
      ctrlinfo++;
    }
  }
  return n;
}

/*----*/
                                                           /* Set Audio-Level */
static signed short audio__setmixlvl (unsigned short mixlvl,                   \
                                      unsigned char mixmux,                    \
                                      const struct audio_mixctrlinfo *info,    \
                                      unsigned char mixdev)
{
  union audio_mixctrldata data;
  unsigned char x,z;
  signed short n,m;
  DWORD uval;
  LONG bval;

  n = 0;
  for (x=0; x<K_AUDIO_MAXMIXCTRL; x++)
  {
    if (info->valid == K_AUDIO_DEVICE_NOHW)
      break;
    else if (info->valid == K_AUDIO_DEVICE_OK)
    {
      switch (info->ctrlinfo.dwControlType)
      {
        case MIXERCONTROL_CONTROLTYPE_MUTE:
          if (mixlvl)
            bval = FALSE;
          else
            bval = TRUE;
          m = audio___setmixpar_b (&data, bval, info);
          goto set_val;
        case MIXERCONTROL_CONTROLTYPE_MUX:
          m = audio___setmixpar_m (&data, mixmux, info);
          goto set_val;
        case MIXERCONTROL_CONTROLTYPE_VOLUME:
          if (mixlvl == 0xFFFF)
            uval = info->ctrlinfo.Bounds.dwMaximum;
          else if (mixlvl == 0x0000)
            uval = info->ctrlinfo.Bounds.dwMinimum;
          else
          {
            uval = info->ctrlinfo.Bounds.dwMaximum;
            uval -= info->ctrlinfo.Bounds.dwMinimum;
            z = 0;
            while (uval & 0xFFFF0000L)
            {
              uval >>= 1;
              z++;
              if (z > 15)
                break;
            }
            uval *= mixlvl;
            z = 16 - z;
            if (z)
            {
              uval += (1L << (z - 1));
              uval >>= z;
            }
            uval += info->ctrlinfo.Bounds.dwMinimum;
          }
          m = audio___setmixpar_u (&data, uval, info);
          set_val:
          if (m >= 0)
            m = audio___setmixdata (&data, info, mixdev);
          if ((m < 0) && (n >= 0))
            n = m;
          break;
      }
    }
    info++;
  }
  return n;
}

/*====*/
                                                      /* Write Bool-Parameter */
static signed short audio___setmixpar_b (union audio_mixctrldata *data,        \
                                         LONG value,                           \
                                         const struct audio_mixctrlinfo *info)
{
  MIXERCONTROLDETAILS_BOOLEAN vbuf,*p;
  unsigned char x,y;
  signed short n;

  n = audio___chkmixinfo (info);
  if (n < 0)
    return n;
  vbuf.fValue = value;
  p = &(data->db[0]);
  for (x=0; x<(info->dchan); x++)
    for (y=0; y<(info->ditem); y++)
      memcpy (p++, &vbuf, sizeof (MIXERCONTROLDETAILS_BOOLEAN));
  return n;
}

                                                  /* Write Unsigned-Parameter */
static signed short audio___setmixpar_u (union audio_mixctrldata *data,        \
                                         DWORD value,                          \
                                         const struct audio_mixctrlinfo *info)
{
  MIXERCONTROLDETAILS_UNSIGNED vbuf,*p;
  unsigned char x,y;
  signed short n;

  n = audio___chkmixinfo (info);
  if (n < 0)
    return n;
  vbuf.dwValue = value;
  p = &(data->du[0]);
  for (x=0; x<(info->dchan); x++)
    for (y=0; y<(info->ditem); y++)
      memcpy (p++, &vbuf, sizeof (MIXERCONTROLDETAILS_UNSIGNED));
  return n;
}

                                                    /* Write Signed-Parameter */
static signed short audio___setmixpar_s (union audio_mixctrldata *data,        \
                                         LONG value,                           \
                                         const struct audio_mixctrlinfo *info)
{
  MIXERCONTROLDETAILS_SIGNED vbuf,*p;
  unsigned char x,y;
  signed short n;

  n = audio___chkmixinfo (info);
  if (n < 0)
    return n;
  vbuf.lValue = value;
  p = &(data->ds[0]);
  for (x=0; x<(info->dchan); x++)
    for (y=0; y<(info->ditem); y++)
      memcpy (p++, &vbuf, sizeof (MIXERCONTROLDETAILS_SIGNED));
  return n;
}

                                                       /* Write Mux-Parameter */
static signed short audio___setmixpar_m (union audio_mixctrldata *data,        \
                                         unsigned char value,                  \
                                         const struct audio_mixctrlinfo *info)
{
  MIXERCONTROLDETAILS_BOOLEAN vbuf,*p;
  unsigned char x,y;
  signed short n;

  n = audio___chkmixinfo (info);
  if (n < 0)
    return n;
  p = &(data->db[0]);
  for (x=0; x<(info->dchan); x++)
    for (y=0; y<(info->ditem); y++)
    {
      if (value == y)
        vbuf.fValue = TRUE;
      else
        vbuf.fValue = FALSE;
      memcpy (p++, &vbuf, sizeof (MIXERCONTROLDETAILS_BOOLEAN));
    }
  return n;
}

/*----*/
                                                           /* Get Mixer-Value */
static signed short audio___getmixdata (union audio_mixctrldata *data,         \
                                        const struct audio_mixctrlinfo *info,  \
                                        unsigned char mixdev)
{
  MIXERCONTROLDETAILS datahdr;
  signed short n;
  MMRESULT rc;

  n = audio___prepmixhdr (&datahdr, data, info);
  if (n < 0)
    return n;
  rc = mixerGetControlDetails ((HMIXEROBJ)mixdev, &datahdr, \
                               MIXER_OBJECTF_MIXER          \
                             | MIXER_GETCONTROLDETAILSF_VALUE);
  if (rc != MMSYSERR_NOERROR)
    n = audio__errconv (rc);
  return n;
}

                                                           /* Set Mixer-Value */
static signed short audio___setmixdata (const union audio_mixctrldata *data,   \
                                        const struct audio_mixctrlinfo *info,  \
                                        unsigned char mixdev)
{
  MIXERCONTROLDETAILS datahdr;
  signed short n;
  MMRESULT rc;

  n = audio___prepmixhdr (&datahdr, (union audio_mixctrldata *)data, info);
  if (n < 0)
    return n;
  rc = mixerSetControlDetails ((HMIXEROBJ)mixdev, &datahdr, \
                               MIXER_OBJECTF_MIXER          \
                             | MIXER_GETCONTROLDETAILSF_VALUE);
  if (rc != MMSYSERR_NOERROR)
    n = audio__errconv (rc);
  return n;
}

/*----*/
                                                   /* Prepare Transfer-Header */
static signed short audio___prepmixhdr (MIXERCONTROLDETAILS *datahdr,          \
                                        union audio_mixctrldata *data,         \
                                        const struct audio_mixctrlinfo *info)
{
  unsigned char a,b;
  signed short n;

  n = audio___chkmixinfo (info);
  if (n < 0)
    return n;
  a = info->dchan;
  b = info->ditem;
  if ((info->ctrlinfo.fdwControl & MIXERCONTROL_CONTROLF_MULTIPLE) == 0)
    b = 0;
  if ((info->ctrlinfo.dwControlType \
      & MIXERCONTROL_CT_UNITS_MASK) == MIXERCONTROL_CT_UNITS_CUSTOM)
  {
    a = 0;
    b = 0;
  }
  datahdr->cbStruct       = sizeof (MIXERCONTROLDETAILS);
  datahdr->dwControlID    = info->ctrlinfo.dwControlID;
  datahdr->cChannels      = a;
  datahdr->cMultipleItems = b;
  datahdr->cbDetails      = info->dsize;
  datahdr->paDetails      = (void *)data;
  return n;
}

/*----*/
                                                         /* Check Info-Values */
static signed short audio___chkmixinfo (const struct audio_mixctrlinfo *info)
{
  unsigned long x;

  if (info->valid != K_AUDIO_DEVICE_OK)
    return E_AUDIO_INTERNAL;
  if (info->dchan > K_AUDIO_MAXMIXCHAN)
    return E_AUDIO_INTERNAL;
  if (info->ditem > K_AUDIO_MAXMIXITEM)
    return E_AUDIO_INTERNAL;
  if (info->dsize == 0xFF)
    return E_AUDIO_INTERNAL;
  x = info->dchan;
  x = x * info->ditem;
  x = x * info->dsize;
  if (x > sizeof (union audio_mixctrldata))
    return E_AUDIO_NOMEM;
  return (signed short)x;
}
#endif

/*----------------------------------------------------------------------------*/

/*--------------------*/
/*  Error-Conversion  */
/*--------------------*/

static signed short audio__errconv (MMRESULT rc)        /* Convert Error-Code */
{
  signed short n;

  switch (rc)
  {
    case MMSYSERR_ALLOCATED:
      n = E_AUDIO_MM_INUSE;
      break;
    case MMSYSERR_BADDEVICEID:
    case MMSYSERR_INVALHANDLE:
    case MMSYSERR_HANDLEBUSY:
    case WAVERR_STILLPLAYING:
      n = E_AUDIO_MM_INTERNAL;
      break;
    case MMSYSERR_NODRIVER:
      n = E_AUDIO_MM_NOSOFT;
      break;
    case MMSYSERR_NOMEM:
      n = E_AUDIO_MM_NOMEM;
      break;
    case WAVERR_BADFORMAT:
      n = E_AUDIO_MM_FORMAT;
      break;
    default:
      n = E_AUDIO_MM_UNKNOWN;
  }
  return n;
}


/*** EOF ***/
